<?php

/**
 * Project:     phpArmory: fetch and unserialize XML data from the World of Warcraft Armory website.
 * File:		phpArmory.class.php
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * For questions, help, comments, discussion, etc., please join the
 * phpArmory mailing list. Send a blank e-mail to
 * smarty-general-subscribe@lists.php.net
 *
 * @link http://sourceforge.net/projects/phparmory/
 * @copyright 2007 Michael Cotterell
 * @author Michael Cotterell <mepcotterell@gmail.com>
 * @package phpArmory
 * @version 0.2
 *
 */
 
 /* $Id: phpArmory.class.php,v 0.1 2007/06/02 */


/**
 * phpArmory Class
 * 
 * A class library to fetch and unserialize XML data from the World of Warcraft Armory website. 
 *
 * @package phpArmory
 */
class armory {

	/**
     * The URL of the Armory website
     *
     * @var string
     */
	var $armory = "http://eu.wowarmory.com/";
	
	/**
     * The case sensitive name of a realm.
     *
     * @var string
     */
	var $realm = "Runetotem";
	
	/**
     * The case sensitive name of a guild.
     *
     * @var string
     */
	var $guild = "Raminghi di Azeroth";
	
	/**
     * The case sensitive name of a character.
     *
     * @var string
     */
	var $character = FALSE;
	
	/**
     * The default user agent for making HTTP requests
     *
     * @var string
     */
	var $userAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.1) Gecko/20061204 Firefox/2.0.0.1";
	
	/**
     * Proxy url:port eg http://160.76.xxx.xxx:8080
     *
     * @var string
     */
	var $proxyURL = FALSE;
	/**
     * Proxy uport eg 8080
     *
     * @var string
     */
	var $proxyPort = FALSE;
	/**
     * Proxy Username:password
     *
     * @var string
     */
	var $proxyUP = FALSE;
	
	/**
     * The amounto of time in senconds after which to consider
	 * a connection timed out if not data has been yet been
	 * received.
     *
     * @var integer
     */
	var $timeout = 120;
	 
	/**#@-*/
	/**
	* The Constructor
	*
	* This function is called when the object is created. It has
	* one optional parameter which sets the base url of the
	* Armory website that will be used to fetch the serialized
	* XML data. (Useful for connecting to the European Armory)
	*
	* @param string	$armory	URL of the Armory website
	*/
	
	var $error = null;
	
	
	function armory($armory = NULL){
	
		if ($armory){
			$this->armory = $armory;
		}
		
	}

	/**
	* characterFetch
	*
	* This function returns the unserialized XML data for a character
	* from the Armory. Both parameters are optional if their
	* corresponding instance variables are set. Most of the
	* time it is safe to assume that the realm instance 
	* variable is set. Therefore, most of the time, the 
	* second paramater is optional whereas the first
	* parameter usually needs to be defined. It is very
	* important to remember that both paramaters are case
	* sensitive.
	*
	* @return string[]			An associative array
	* @param string	$character	The name of the character
	* @param string	$realm		The character's realm
	*/
	function characterFetch($character = NULL, $realm = NULL){
		
		if(($character==NULL)&&($this->character)) $character = $this->character;
		if(($realm==NULL)&&($this->realm)) $realm = $this->realm;
		
		$url = $this->armory."character-sheet.xml?r=".str_replace(" ", "+",$realm)."&n=".str_replace(" ", "+", urlencode(utf8_encode($character)));
		echo $url;
		$xmlFetxh = $this->xmlFetch($url);
		if($xmlFetxh)
		{
			return  $this->xmlToArray($xmlFetxh);
		}
		else 
		{
			return false;
		}
	
		
	}
	
	/**
	* guildFetch
	* 
	* This function returns the unserialized XML data for a Guild
	* from the Armory. Both parameters are optional if their
	* corresponding instance variables are set. Most of the
	* time it is safe to assume that the realm instance 
	* variable is set. Therefore, most of the time, the 
	* second paramater is optional whereas the first
	* parameter usually needs to be defined. It is very
	* important to remember that both paramaters are case
	* sensitive.
	*
	* @return string[]		An associative array
	* @param string	$guild	The name of the guild
	* @param string	$realm	The guild's realm
	*/
	function guildFetch($guild = NULL, $realm = NULL){
	
		if(($guild==NULL)&&($this->guild)) $guild = $this->guild;
		if(($realm==NULL)&&($this->realm)) $realm = $this->realm;
	
		$url = $this->armory."guild-info.xml?r=".str_replace(" ", "+",$realm)."&n=".str_replace(" ", "+",urlencode(utf8_encode($guild)));
		$xmlFetxh = $this->xmlFetch($url);
		if($xmlFetxh)
		{
			return  $this->xmlToArray($xmlFetxh);
		}
		else 
		{
			return false;
		}
	
	
	}
	
	/**
	* itemFetch
	* 
	* This function returns the unserialized XML data
	* for an item from the Armory. The itemID parameter
	* is required.
	*
	* @return string[]				An associative array
	* @param integer	$itemID		The ID of the item
	*/
	function itemFetch($itemID){
	
		$url = $this->armory."item-tooltip.xml?i=".$itemID;
		$xmlFetxh = $this->xmlFetch($url);
		if($xmlFetxh)
		{
			return  $this->xmlToArray($xmlFetxh);
		}
		else 
		{
			return false;
		}
	
	}
	
	/**
	* xmlFetch
	* 
	* This function returns the string of characters
	* returned from an HTTP GET request to the url 
	* defined in the url parameter. It is interesting
	* to note that although the function is called 
	* xmlFetch, the returned string may not neccesarily
	* be serialized XML data when the function is 
	* called publicly. 
	*
	* @todo Make the function independent of cURL
	*
	* @param string		$url			URL of the page to fetch data from
	* @param string		$userAgent		The user agent making the GET request
	* @param integer	$timeout		The connection timeout in seconds
	*/
	function xmlFetch($url, $userAgent = NULL, $timeout = NULL){
	
		if(($userAgent==NULL)&&($this->userAgent)) $userAgent = $this->userAgent;
		if(($timeout==NULL)&&($this->timeout)) $timeout = $this->timeout;
	
		if (function_exists('curl_init')){
			
			$ch = curl_init();
			$timeout = $this->timeout;
			$userAgent = $this->userAgent;
			

			curl_setopt ($ch, CURLOPT_RETURNTRANSFER, 1);
			curl_setopt ($ch, CURLOPT_CONNECTTIMEOUT, $timeout);
			curl_setopt ($ch, CURLOPT_USERAGENT, $userAgent);
			curl_setopt ($ch, CURLOPT_FAILONERROR,TRUE);
			

			if($this->proxyURL)
			{
				curl_setopt ($ch, CURLOPT_PROXY, $this->proxyURL); 
				curl_setopt ($ch, CURLOPT_PROXYPORT,$this->proxyPort); 
			}
			if($this->proxyUP)
			{
				curl_setopt ($ch, CURLOPT_PROXYUSERPWD, $this->proxyUP); 
			}

			curl_setopt ($ch, CURLOPT_URL, $url);
			
			$f = curl_exec($ch);

			if(curl_error($ch))
			{
				$this->error = curl_error($ch);
				return false;
			}
			else 
			{
				echo $f;
				return $f;
			}


			curl_close($ch);
		
		} 
		else 
		{
			return false;
		}

	
	}
	
	/**
	* xmlToArray
	* 
	* This function converts an xml string to an associative array
	* duplicating the xml file structure.
	*
	* @param string		$xmlData 		The XML data string to convert.
	* @param boolean 	$includeTopTag	Whether or not the topmost xml tag should be included in the array. The default value for this is false.
	* @param boolean	$lowerCaseTags	Whether or not tags should be set to lower case. Default value for this parameter is true.
	* @return string[]					An associative array
	* @author Jason Read <jason@ace.us.com>
	*/	
	function & xmlToArray($xmlData, $includeTopTag = false, $lowerCaseTags = true){
	
		$parser = xml_parser_create();
	
		xml_parse_into_struct($parser, $xmlData, $key, $index);
		xml_parser_free($parser);
	
		$xml = array();
		$levels = array();
		$multipleData = array();
		$prevTag = "";
		$currTag = "";
		$topTag = false;
	
		foreach ($key as $val)
		{
			// Open tag
			if ($val["type"] == "open"){
			
				if (!$this->xmlToArrayOpen($topTag, $includeTopTag, $val, $lowerCaseTags, $levels, $prevTag, $multipleData, $xml)){
					continue;
				}
				
			}
			
			// Close tag
			else if ($val["type"] == "close"){
			
				if (!$this->xmlToArrayClose($topTag, $includeTopTag, $val, $lowerCaseTags, $levels, $prevTag, $multipleData, $xml)){
					continue;
				}
				
			}
			
			// Data tag
			else if ($val["type"] == "complete" && isset($val["value"])){
			
				$loc =& $xml;
				
				foreach ($levels as $level){
				
					$temp =& $loc[str_replace(":arr#", "", $level)];
					$loc =& $temp;
				
				}
				
				$tag = $val["tag"];
				
				if ($lowerCaseTags)
				{
				
					$tag = strtolower($val["tag"]);
				
				}
				
				$loc[$tag] = str_replace("\\n", "\n", $val["value"]);
			
			}
			
			// Tag without data
			else if ($val["type"] == "complete"){
			
				$this->xmlToArrayOpen($topTag, $includeTopTag, $val, $lowerCaseTags,$levels, $prevTag, $multipleData, $xml);
				$this->xmlToArrayClose($topTag, $includeTopTag, $val, $lowerCaseTags,$levels, $prevTag, $multipleData, $xml);
			
			}
		}
		
		return $xml;
	
	}
	
	/**
	* xmlToArrayOpen
	*
	* Private support function for xmlToArray. Handles an xml OPEN tag.
	*
	* @param string		&$topTag 		xmloArray topTag variable
	* @param boolean	&$includeTopTag	xmlToArray includeTopTag variable
	* @param string[]	&$val 			xmlToArray val variable
	* @param string		&$currTag		xmlToArray currTag variable
	* @param boolean	&$lowerCaseTags	xmlToArray lowerCaseTags variable
	* @param string[]	&$levels			xmlToArray levels variable
	* @param string		&$prevTag		xmlToArray prevTag variable
	* @param boolean	&$multipleData	xmlToArray multipleData variable
	* @param string[]	&$xml			xmlToArray xml variable
	* @return boolean
	* @author Jason Read <jason@ace.us.com>
	*/
	function xmlToArrayOpen(& $topTag, & $includeTopTag, & $val, & $lowerCaseTags, & $levels, & $prevTag, & $multipleData, & $xml){
	
		// don't include top tag
		if (!$topTag && !$includeTopTag){
	
			$topTag = $val["tag"];
			return false;
	
		}
		
		$currTag = $val["tag"];
	
		if ($lowerCaseTags){
	
			$currTag = strtolower($val["tag"]);
	
		}
	
		$levels[] = $currTag;
	
		// Multiple items w/ same name. Convert to array.
		if ($prevTag === $currTag){
	
			if (!array_key_exists($currTag, $multipleData) || !$multipleData[$currTag]["multiple"]){
	
				$loc =& $xml;
				
				foreach ($levels as $level){
				
					$temp =& $loc[$level];
					$loc =& $temp;
				
				}
	
				$loc = array($loc);
				$multipleData[$currTag]["multiple"] = true;
				$multipleData[$currTag]["multiple_count"] = 0;
			
			}
	
			$multipleData[$currTag]["popped"] = false;
			$levels[] = ":arr#" . ++$multipleData[$currTag]["multiple_count"];
	
		} else { 
	
			$multipleData[$currTag]["multiple"] = false;
		
		}
	
		// Add attributes array
		if (array_key_exists("attributes", $val)){
	
			$loc =& $xml;
	
			foreach ($levels as $level){
			
				$temp =& $loc[str_replace(":arr#", "", $level)];
				$loc =& $temp;
			
			}
			
			$keys = array_keys($val["attributes"]);
			
			foreach ($keys as $key){
			
				$tag = $key;
				
				if ($lowerCaseTags){
			
					$tag = strtolower($tag);
			
				}
				
				$loc["attributes"][$tag] = & $val["attributes"][$key];
	
			}
	
		}
	
		return true;
	
	}
	
	/**
	* xmlToArrayClose
	*
	* Private support function for xmlToArray. Handles an xml OPEN tag.
	*
	* @param string		&$topTag			xmlToArray topTag variable
	* @param boolean	&$includeTopTag	xmlToArray includeTopTag variable
	* @param string[]	&$val			xmlToArray val variable
	* @param string		&$currTag		xmlToArray currTag variable
	* @param boolean	&$lowerCaseTags	xmlToArray lowerCaseTags variable
	* @param string		&$levels			xmlToArray levels variable
	* @param string		&$prevTag		xmlToArray prevTag variable
	* @param boolean	&$multipleData	xmlFileToArray multipleData variable
	* @param stringp[]	&$xml			xmlToArray xml variable
	* @return boolean
	* @author Jason Read <jason@ace.us.com>
	*/
	function xmlToArrayClose(& $topTag, & $includeTopTag, & $val, & $lowerCaseTags, & $levels, & $prevTag, & $multipleData, & $xml){
	
		// don't include top tag
		if ($topTag && !$includeTopTag && $val["tag"] == $topTag){
	
			return false;
	
		}
	
		if ($multipleData[$currTag]["multiple"]){
			
			$tkeys = array_reverse(array_keys($multipleData));
	
			foreach ($tkeys as $tkey){
	
				if ($multipleData[$tkey]["multiple"] && !$multipleData[$tkey]["popped"]){
					
					array_pop($levels);
	
					$multipleData[$tkey]["popped"] = true;
	
					break;
	
				} else if (!$multipleData[$tkey]["multiple"]) {
	
					break;
				
				}
	
			}
		
		}
		
		$prevTag = array_pop($levels);
	
		if (strpos($prevTag, "arr#")){
			
			$prevTag = array_pop($levels);
		
		}
	
		return true;
	
	}

	
	/**#@-*/
	
}

?>